<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="coinfaces">

        <title>关于套接字的一些理解</title>

    <link rel="stylesheet" href="../../theme/css/style.css">
    <link rel="stylesheet" href="../../theme/css/pygments.css">



</head>

<body>
    <div class="content">
<h1>关于套接字的一些理解</h1>    <p>
        under
            <a href="../../tags/socket/">socket</a>
            <a href="../../tags/reading/">reading</a>
    </p>
    <p>
        in <a href="../../categories/tech/">tech</a>
    </p>
    <p>Published: 2016-09-16</p>


    <p>为了方便程序员使用套接字，Python文档专门写了<a class="reference external" href="https://docs.python.org/2.7/howto/sockets.html">一篇Howto</a>，浅显地介绍了套接字的概念以及编程注意事项，读后印象较深的有以下内容。这篇文章和本文都只涉及Python 2.x版本的套接字编程，Python 3.x里套接字编程有所不同，一个显著的区别是Python 3.x只接受bytes类型的收发，不支持字符串收发。</p>
<div class="section" id="id3">
<h2>数据收发均由客户端套接字完成</h2>
<p>服务端套接字只负责监听和接受请求，每接受一个请求，就产生一个客户端套接字和对方通信。</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">socket</span>

<span class="n">server_socket</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
<span class="n">server_socket</span><span class="o">.</span><span class="n">bind</span><span class="p">((</span><span class="s1">&#39;127.0.0.1&#39;</span><span class="p">,</span> <span class="mi">9876</span><span class="p">))</span>
<span class="n">server_socket</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>

<span class="k">try</span><span class="p">:</span>
    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
        <span class="n">client_socket</span><span class="p">,</span> <span class="n">address</span> <span class="o">=</span> <span class="n">server_socket</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Client from </span><span class="si">%s</span><span class="s1">:</span><span class="si">%s</span><span class="s1"> connected&#39;</span> <span class="o">%</span> <span class="n">address</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
    <span class="n">server_socket</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
</div>
<div class="section" id="id4">
<h2>接收端对数据如何发送不知情</h2>
<p>无论发送端是一次发送还是分几次发送，接收端看来都没有区别，略举几个例子说明（这几个例子可以成功的部分原因是用了本机IP，发送和接收都是瞬间完成的，如果网络状况不好，则可能得到不一样的结果），为简便，省略了建立和关闭连接的代码。</p>
<p>一、分批发送，分批接收</p>
<p>发送端：</p>
<div class="highlight"><pre><span></span><span class="n">fragments</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;He&#39;</span><span class="p">,</span> <span class="s1">&#39;llo&#39;</span><span class="p">]</span>

<span class="k">for</span> <span class="n">fragment</span> <span class="ow">in</span> <span class="n">fragments</span><span class="p">:</span>
    <span class="n">client_socket</span><span class="o">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">fragment</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</pre></div>
<p>接收端：</p>
<div class="highlight"><pre><span></span><span class="n">chunks</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
    <span class="n">chunk</span> <span class="o">=</span> <span class="n">client_socket</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">chunk</span><span class="p">:</span>
        <span class="n">chunks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">break</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;It says </span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">chunks</span><span class="p">)</span>
</pre></div>
<p>接收端显示：<tt class="docutils literal">It says ['He', 'llo']</tt></p>
<p>二、分批发送，一次接收：</p>
<p>发送端：</p>
<div class="highlight"><pre><span></span><span class="n">fragments</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;He&#39;</span><span class="p">,</span> <span class="s1">&#39;llo&#39;</span><span class="p">]</span>

<span class="k">for</span> <span class="n">fragment</span> <span class="ow">in</span> <span class="n">fragments</span><span class="p">:</span>
    <span class="n">client_socket</span><span class="o">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">fragment</span><span class="p">)</span>
</pre></div>
<p>接收端：</p>
<div class="highlight"><pre><span></span><span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.2</span><span class="p">)</span>
<span class="n">chunks</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
    <span class="n">chunk</span> <span class="o">=</span> <span class="n">client_socket</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">chunk</span><span class="p">:</span>
        <span class="n">chunks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">break</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;It says </span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">chunks</span><span class="p">)</span>
</pre></div>
<p>接收端显示：<tt class="docutils literal">It says ['Hello']</tt></p>
<p>三、一次发送，分批接收</p>
<p>发送端：</p>
<div class="highlight"><pre><span></span><span class="n">fragments</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;Hello&#39;</span><span class="p">]</span>

<span class="k">for</span> <span class="n">fragment</span> <span class="ow">in</span> <span class="n">fragments</span><span class="p">:</span>
    <span class="n">client_socket</span><span class="o">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">fragment</span><span class="p">)</span>
</pre></div>
<p>接收端：</p>
<div class="highlight"><pre><span></span><span class="n">chunks</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
    <span class="n">chunk</span> <span class="o">=</span> <span class="n">client_socket</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">chunk</span><span class="p">:</span>
        <span class="n">chunks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">break</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;It says </span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">chunks</span><span class="p">)</span>
</pre></div>
<p>接收端显示：<tt class="docutils literal">It says ['He', 'll', 'o']</tt></p>
<p>四、分批发送，分批接收，但不完全一致</p>
<p>发送端：</p>
<div class="highlight"><pre><span></span><span class="n">fragments</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;Hel&#39;</span><span class="p">,</span> <span class="s1">&#39;lo&#39;</span><span class="p">]</span>

<span class="k">for</span> <span class="n">fragment</span> <span class="ow">in</span> <span class="n">fragments</span><span class="p">:</span>
    <span class="n">client_socket</span><span class="o">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">fragment</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</pre></div>
<p>接收端：</p>
<div class="highlight"><pre><span></span><span class="n">chunks</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.06</span><span class="p">)</span>
    <span class="n">chunk</span> <span class="o">=</span> <span class="n">client_socket</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">chunk</span><span class="p">:</span>
        <span class="n">chunks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">break</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;It says </span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">chunks</span><span class="p">)</span>
</pre></div>
<p>接收端显示：<tt class="docutils literal">It says ['He', 'll', 'o']</tt></p>
<p>如果一次连接中要收发多段消息，如何知道每段消息的开始和结束就只能靠程序员事先设计好消息格式，然后在程序里判断了，文章里给了三种可能的解决方案：</p>
<ul class="simple">
<li>所有消息是固定长度的</li>
<li>用分隔符隔开各段消息</li>
<li>在消息内写明其长度</li>
</ul>
</div>
<div class="section" id="id5">
<h2>字节序</h2>
<p>套接字也可用于收发二进制数据，但多字节数据存在字节序转换的问题，一般网络上都使用“大端”字节序，而普通PC则使用小端，不加转换使用容易产生问题：</p>
<p>发送端：</p>
<div class="highlight"><pre><span></span><span class="n">client_socket</span><span class="o">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s1">&#39;H&#39;</span><span class="p">,</span> <span class="mi">5</span><span class="p">))</span>
</pre></div>
<p>接收端：</p>
<div class="highlight"><pre><span></span><span class="n">chunks</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
    <span class="n">chunk</span> <span class="o">=</span> <span class="n">client_socket</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">chunk</span><span class="p">:</span>
        <span class="n">chunks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">break</span>
<span class="n">number</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">ntohs</span><span class="p">(</span><span class="n">struct</span><span class="o">.</span><span class="n">unpack</span><span class="p">(</span><span class="s1">&#39;H&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">chunks</span><span class="p">))[</span><span class="mi">0</span><span class="p">])</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;It says </span><span class="si">%d</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">number</span><span class="p">)</span>
</pre></div>
<p>接收端显示：<tt class="docutils literal">It says 1280</tt></p>
<p>接收端遵守规矩，将网络上的短整数表达转化成了本地表达，但发送方没有在发送前转化短整数表达方式，导致信息误传，如果发送方在将整数传递给<tt class="docutils literal">struct.pack</tt>函数之前，先用<tt class="docutils literal">socket.htons</tt>转化，则双方都安全地假定传过来的数据是网络格式，即使不了解对方怎么表达短整数，也不会产生问题。</p>
</div>
<div class="section" id="sendsendall">
<h2>send和sendall</h2>
<p>如果仔细看文章中定长消息的那个例子，会发现<tt class="docutils literal">socket.send</tt>函数有一个坑，即它不一定将所有内容都发送出去，事实上可以用它是否返回0（实际发送的字节数）来判断套接字连接是否已被关闭。<tt class="docutils literal">socket.sendall</tt>函数可以保证内容被全部发送出去，如果做不到则会产生一个异常。无论哪个函数都<strong>不保证</strong>已发送的内容成功到达对方一侧。</p>
<p>（完）</p>
</div>

    </div>
<nav class="nav sidebar">
    <div class="siteinfo">
        <div class="sitename"><a href="../..">coinfaces</a></div>
        <div class="tagline">Another Side of Life</div>
    </div>

        <a href="../../categories/">categories</a>
        <a href="../..">&larr; Home</a>
</nav><footer class="footer">
    <span>
        &copy; jpli &middot;
        under <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0</a>
    </span>
    |
    <span>
        Powered by:
        <a href="http://blog.getpelican.com/">Pelican</a>
    </span>
</footer></body>
</html>